package com.fadadianqi.search;


import com.yeqiangwei.cache.Cache;
import com.yeqiangwei.commons.net.URL;
import com.yeqiangwei.commons.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.thesaurus.ThesaurusAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.*;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.wltea.analyzer.lucene.IKQueryParser;
import org.wltea.analyzer.lucene.IKSimilarity;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractSearcher {

    private static final Log log = LogFactory.getLog(AbstractSearcher.class);

    protected static final String PROP_ID = "id";

    private static Analyzer analyzer;

    public AbstractSearcher() {
        analyzer = new ThesaurusAnalyzer();
    }

    protected Analyzer getAnalyzer() {
        return analyzer;
    }

    public Result search(HttpServletRequest request, Pagination pagination) throws Exception {
        String q = ParamUtils.getString(request, "q", null);
        if (q == null) {
            return null;
        }
        String keywords = URL.decode(q, Constants.UTF_8, q);
        Map<String, Object> elements = new HashMap<String, Object>(); // 分页参数
        elements.put("q", q);
        Result result = null;
        if (StringHelper.length(keywords) < 100
                && SearchUtils.charIsLetter(keywords)) {
            // 如果是英文且长度很短，则默认加引号执行一次搜索.
            result = search("\"" + keywords + "\"", pagination);
            if (ValidatorHelper.isEmpty(result.getItems())) {
                // 如果英文加引号搜不到东西则去掉引号再搜一遍
                result = search(keywords, pagination);
            }
        } else if (!SearchUtils.charIsLetter(keywords)) {
            // 中文搜索关键词优化开始
            String cnkeywords = (String) cnKeywordsCache.get("cn_keywords_" + q);
            if (ValidatorHelper.isEmpty(cnkeywords)) {
                cnkeywords = SearchUtils.optimizeChineseKeywords(q);
                cnKeywordsCache.put("cn_keywords_" + q, cnkeywords);
            }
            // 中文搜索关键词优化结束
            result = search(cnkeywords, pagination);
            //result = search(keywords, pagination);
        } else {
            result = search(keywords, pagination);
        }
        result.setOrigiKey(keywords); // 缓存未被修改过的关键词
        pagination.setTotal(result.getTotal());
        pagination.setPageNode(pagination.getPageNode());
        pagination.setMaxResults(pagination.getMaxResults());
        pagination.setElements(elements);
        result.setPagination(pagination);
        return result;
    }

    /*
     * 		Argument argument = Argument.getInstance();
        argument.setPagination(pagination);
        argument.setArgs(SellIndexService.FIELD_DETAILS, keywords, Occur.SHOULD, 0.9F);
        argument.setArgs(SellIndexService.FIELD_CUSTOMER_NAME, keywords, Occur.SHOULD, 0.5F);
        argument.setArgs(SellIndexService.FIELD_VILLAGE_NAME, keywords, Occur.SHOULD, 0.5F);
        argument.setArgs(SellIndexService.FIELD_TOWN_NAME, keywords, Occur.SHOULD, 0.5F);
        Sort sort = new Sort(new SortField(AbstractIndexService.ADD_TIME, SortField.LONG, true));
        argument.setSort(sort);
        Query rangeQuery = NumericRangeQuery.newLongRange(ADD_TIME, min, max, Boolean.TRUE, Boolean.TRUE);

     */
    public Result search(String keywords, Query query, Sort sort, Pagination pagination)
            throws Exception {
        Argument arg = initializtionArgument(keywords, pagination);
        arg.addQuery(query);
        arg.setSort(sort);
        return search(arg);
    }

    public Result search(String keywords, Query query, Pagination pagination)
            throws Exception {
        return search(keywords, query, null, pagination);
    }

    public Result search(String keywords, Pagination pagination)
            throws Exception {
        return search(keywords, null, null, pagination);
    }

    protected abstract Argument initializtionArgument(String keyword,
                                                      Pagination pagination);


    private Query luceneQueryBuilder(Argument argument) throws IOException {
        List<String> _keywords = argument.getKeywords();
        for (int i = 0; i < _keywords.size(); i++) {
            // 繁体转简体英文加
            _keywords.set(i, SearchUtils.filterKeys(argument.getKeywords().get(i)));
        }
        BooleanClause.Occur[] flags = argument.getOccurArray();
        String[] fields = argument.getFieldArray();
        String[] keywords = argument.getKeywordArray();
        BooleanQuery query = (BooleanQuery) IKQueryParser.parseMultiField(fields, keywords, flags);
        if (ValidatorHelper.isNotEmpty(argument.queryList)) {
            for (Query q : argument.queryList) {
                query.add(q, Occur.MUST);
            }
        }
        return query;
    }

    private IndexSearcher luceneSearcherBuilder() throws Exception {
        String indexPath = getIndexPath();
        if (log.isInfoEnabled()) {
            log.info("#luceneSearcherBuilder# indexPath: " + indexPath);
        }
        Directory directory = SearchUtils.getDirectory(indexPath);
        IndexSearcher indexSearcher = new IndexSearcher(IndexReader.open(directory, null, true));
        indexSearcher.setSimilarity(new IKSimilarity());
        return indexSearcher;
    }

    public Result search(Argument argument) throws Exception {
        Result result = new Result();

        result.setKey(argument.getKeywords().get(0));
        String origiKey = URL.decode(result.getKey(), Constants.UTF_8, result.getKey());
        result.setOrigiKey(origiKey);

        if (ValidatorHelper.isEmpty(result.getKey())) {
            return result;
        }
        if (ValidatorHelper.isEmpty(argument.getKeywords())) {
            return result;
        }
        long runtimed = 0;
        long begin = System.currentTimeMillis();

        Query query = luceneQueryBuilder(argument);
        IndexSearcher searcher = luceneSearcherBuilder();

        int maxResults = 5000; // 返回的最多结果条数
        //TopScoreDocCollector collector = TopScoreDocCollector.create(maxResults, true);
        //searcher.search(query, collector);
        //ScoreDoc[] hits = collector.topDocs().scoreDocs;
        //Sort sort = new Sort(new SortField(AbstractIndexService.ADD_TIME, SortField.LONG, true));
        TopDocs docs = null;
        if (argument.getSort() != null) {
            docs = searcher.search(query, maxResults, argument.getSort());
        } else {
            docs = searcher.search(query, maxResults);
        }
        ScoreDoc[] hits = docs.scoreDocs;
        //ScoreDoc[] hits = searcher.search(query, maxResults, sort).scoreDocs;
        if (query == null || searcher == null) {
            if (log.isDebugEnabled()) {
                log.debug("...... query == null || searcher == null || collector == null");
                log.debug(query + " / " + searcher + " / ");
            }
            return result;
        }
        if (hits == null || hits.length == 0) {
            log.debug("...... hits == null || hits.length == 0");
            return result;
        }
        Integer totalrows = docs.totalHits;
        /*if (collector.getTotalHits() > maxResults) {
			totalrows = maxResults;
		} else {
			totalrows = collector.getTotalHits();
		}*/
        QueryScorer queryScorer = new QueryScorer(query);
        Pagination pagination = argument.getPagination();
        int listrows = pagination.getMaxResults(); // 每页显示行数
        int startrows = 0; // 显示记录集的起始行
        int maxrows = listrows; // 当前页最大行数
		/* 翻页滤错开始 */
        if (listrows > totalrows) {
            listrows = totalrows;
        }
        if (pagination.getPage() > 1) {
            startrows = (listrows * (pagination.getPage() - 1));
            maxrows = startrows + listrows;
        }
        if (maxrows > totalrows) {
            maxrows = totalrows;
        }
        List<Object> items = new ArrayList<Object>();
        Highlighter highlighter = null;
        if (argument.getHighlight()) {
            SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter(
                    FONT_BEGIN, FONT_END);
            highlighter = new Highlighter(simpleHTMLFormatter, queryScorer);
            highlighter.setTextFragmenter(new SimpleFragmenter(100));
        }
        for (int i = startrows; i < maxrows; i++) {
            Document doc = searcher.doc(hits[i].doc);
            items.add(documentToVO(searcher, doc, highlighter));
        }
        searcher.close();
        result.setItems(items);
        result.setTotal(totalrows);
        pagination.setTotal(totalrows);
        result.setPagination(pagination);
        long end = System.currentTimeMillis();
        runtimed = (end - begin);
        result.setRuntimed(runtimed);
        return result;
    }

    protected static final String FONT_BEGIN = "<font color=\"#ff0000\">";
    protected static final String FONT_END = "</font>";
    protected static final String APPEND_STR = " yqw";
    protected static final int APPEND_LEN = 3;

    /**
     * @param searcher
     * @param doc
     * @param highlight 是否给结果高亮
     * @return
     * @throws IOException
     */
    protected abstract Object documentToVO(IndexSearcher searcher, Document doc,
                                           Highlighter highlighter) throws IOException;


    protected String highlightField(Analyzer analyzer, Highlighter highlighter
            , String fieldName, String fieldValue) throws IOException {
        String str = fieldValue + APPEND_STR;
        TokenStream ts_1 = analyzer.tokenStream(fieldName
                , new StringReader(str));
        String value;
        try {
            value = highlighter.getBestFragment(ts_1, str);
        } catch (InvalidTokenOffsetsException e) {
            throw new IOException(e);
        }
        if (value == null) {
            return fieldValue;
        } else if (value.endsWith(APPEND_STR)) {
            return StringHelper.substring(value, 0, value.length() - APPEND_LEN, value);
        }
        return value;
    }

    protected abstract String getIndexPath();

    protected Cache<String> cnKeywordsCache;

    public void setCnKeywordsCache(Cache<String> cnKeywordsCache) {
        this.cnKeywordsCache = cnKeywordsCache;
    }

}
